<!doctype html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible"
          content="ie=edge">
    <script src="jsplumb2.8.4.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
    <title>拖拽流Demo</title>
    <style>
    #diagramContainer {
        user-select: none;
        padding: 20px;
        width: 80%;
        height: 600px;
        border: 1px solid gray;
        position: relative;
    }

    .item {
        position: absolute;
        height: 60px;
        width: 150px;
        border: 1px solid #66B366;
        display: flex;
        align-items: center;
        justify-content: center;
        border-radius: 5px;
        background: #ddd;
    }
    .btn-ct{
        display: flex;
        justify-content: flex-start;
        margin-bottom: 10px;
    }
    .btn{
        border: 1px solid #ddd;
        border-radius: 3px;
        padding: 10px;
        cursor: pointer;
        user-select: none;
        margin: 0 5px;
    }
    .aLabel {
        background-color:white;
        opacity:0.8;
        padding:0.3em;
    }

    </style>
</head>
<body>
<div id="vue-ct">
    <h3>
        {{ message }}
    </h3>
    <div class="btn-ct">
        <div class="btn" @click="addEl()">
            添加节点
        </div>
        <div class="btn" @click="clearElCon()">
            断开第一个元素的所有链接
        </div>
        <div class="btn" @click="removeEl()">
            删除第一个元素
        </div>
        <div class="btn" @click="addConFromData()">
            所有块和起点建立链接
        </div>
        <div class="btn" @click="reset()">
            清空
        </div>
    </div>
    <div id="diagramContainer">
        <template v-for="(item,index) in elementsList">
            <div :id="item.id" :style="item.style" class="item">
                <template v-if="index === 0">
                    假装是起点
                </template>
                <template v-else>
                    流程
                </template>
            </div>
        </template>

    </div>
</div>

<script>

var app = new Vue({
    el: '#vue-ct',
    data: {
        message: '拖拽流Demo：Vue + jsPlumb',
        // jsPlumb实例
        _jsPlumb:null,
        // 基本设置
        commonOption:{
            // 拖拽设置
            DragOptions: {
                cursor: 'pointer',
                zIndex: 2000,
                containment: 'diagramContainer',
                grid: [5, 5],
            },
            Connector: [ "Flowchart", { stub: [40, 60], gap: 10, cornerRadius: 5, alwaysRespectStubs: true } ],
            // 元素连接点的样式
            EndpointStyle: { fill: 'transparent',radius:0 },
            // 连接线端点样式
            // Endpoints : [ [ "Dot", { radius:7 } ], [ "Dot", { radius:7 } ] ],
            // EndpointStyles : [{ fill: 'transparent',radius:0 }, { fill: 'transparent',radius:0 }],
            // 连接线样式
            PaintStyle: {
                stroke: "rgba(122, 176, 44, 0.6)",
                fill: "transparent",
                radius: 5,
                strokeWidth: 3
            },
            // 连接线hover样式
            HoverPaintStyle: {
                stroke: "red",
                strokeWidth: 3,
            },
            // 添加连接线箭头和label
            ConnectionOverlays: [
                [ "Arrow", {
                    location: 1,
                    visible:true,
                    width:11,
                    length:11,
                    id:"ARROW",
                    events:{
                        click:function() { alert("你点击了箭头！")}
                    }
                } ],
            ],
            MaxConnections:-1,
            // 设置容器
            Container: "diagramContainer",
        },
        // 源元素设置
        sourceEndpointOpt : {
            isSource: true,
            endpoint: "Dot",
            connector: [ "Flowchart", { stub: [40, 60], gap: 10, cornerRadius: 5, alwaysRespectStubs: true } ],
            paintStyle: {
                stroke: "#7AB02C",
                fill: "transparent",
                radius: 7,
                strokeWidth: 1
            },
            hoverPaintStyle: {
                // fill: "#216477",
                // stroke: "#216477",
                stroke: "rgba(122, 176, 44, 0.6)",
                fill: "rgba(122, 176, 44, 0.6)",
            },
            dragOptions: {},
            overlays: [
                [ "Label", {
                    location: [0.5, 1.5],
                    label: "Drag",
                    cssClass: "endpointSourceLabel",
                    visible:false
                } ]
            ]
        },
        // 目标元素设置
        targetEndpointOpt : {
            isTarget: true,
            endpoint: "Dot",
            paintStyle: { fill: "rgba(122, 176, 44, 0.6)", radius: 7 },
            hoverPaintStyle: {
                // fill: "#216477",
                // stroke: "#216477"
                stroke: "rgba(122, 176, 44, 0.6)",
                fill: "rgba(122, 176, 44, 0.6)",
            },
            dropOptions: { hoverClass: "hover", activeClass: "active" },
            overlays: [
                [ "Label", { location: [0.5, -0.5], label: "Drop", cssClass: "endpointTargetLabel", visible:false } ]
            ]
        },
        elementsList:[
            {
                id:'item_test1',
                sourceAnchors: ['BottomCenter'],
                targetAnchors: [],
                style:{
                    top:'300px',
                    left: '400px'
                }
            },
        ]
    },
    methods:{
        jsPlumbInit(){
            let _this = this;
            jsPlumb.ready(function () {
                // 创建单独的实例
                _this.createInstance();
                // 添加元素
                _this.elementsList.map(item => {
                    _this.jsPlumbAddItem(item);
                });
                // 添加事件
                _this.listenEvent();
            })
        },
        createInstance(){
            let _this = this;
            _this._jsPlumb = jsPlumb.getInstance(_this.commonOption);
        },
        jsPlumbAddItem(item){
            let _this = this;
            // 添加锚点
            let {sourceAnchors,targetAnchors,id} = item;
            sourceAnchors.map((sourceAnchor) => {
                let sourceUUID = id + sourceAnchor;
                _this._jsPlumb.addEndpoint(id, _this.sourceEndpointOpt, {
                    anchor: sourceAnchor,
                    uuid: sourceUUID
                });
            });
            targetAnchors.map((targetAnchor) => {
                let targetUUID = id + targetAnchor;
                _this._jsPlumb.addEndpoint(id, _this.targetEndpointOpt, {
                    anchor: targetAnchor,
                    uuid: targetUUID
                });
            });
            // 添加元素可拖动属性
            _this._jsPlumb.draggable(item.id);
            // 防止回环连接(好像没什么用)
            // _this._jsPlumb.makeTarget(item.id, {
            //     allowLoopback:false
            // });
        },
        listenEvent(){
            let _this = this;
            // 添加连接线点击事件
            _this._jsPlumb.bind('click', function (conn, originalEvent) {
                console.log(conn)
                _this._jsPlumb.deleteConnection(conn);
            });
            // 连接前事件
            _this._jsPlumb.bind('beforeDrop', function (info) {
                console.log('连接前:',info);
                return _this.whetherConnect(info.connection.sourceId,info.connection.targetId);
            });
            // 连接事件
            _this._jsPlumb.bind('connection', function (info) {
                console.log('已连接:',info);
                return true;
            });
            // 连接Detached事件
            _this._jsPlumb.bind('connectionDetached', function (info) {
                console.log('连接Detached:',info);
                return true
            });
            // 连接Moved事件
            _this._jsPlumb.bind('connectionMoved', function (info) {
                console.log('连接Moved:',info);
                return true
            })
        },
        // 计算是否能够连接
        whetherConnect(sourceId,targetId){
            let _this = this;
            let canConnect = true;
            // 判断重复连接
            let fmtArr = _this._jsPlumb.getConnections().map(item => {
                if(item.sourceId === sourceId && item.targetId === targetId){
                    canConnect = false;
                    console.log('该连接为重复连接')
                }
                return {
                    sourceId:item.sourceId,
                    targetId:item.targetId,
                }
            });
            if(!canConnect){
                return canConnect
            }

            // 有向无环图
            let hasMatchA = false;
            function DAG(arr,fromA,toB) {
                arr.map((item,index) => {
                    if(item.sourceId === toB){
                        if(item.targetId === fromA){
                            hasMatchA = true;
                        }else{
                            let _arr = [...arr];
                            _arr.splice(index,1);
                            DAG(_arr,fromA,item.targetId)
                        }
                    }
                })
            }
            DAG(fmtArr,sourceId,targetId);
            if(hasMatchA){
                canConnect = false;
                alert('形成回环')
            }
            return canConnect
        },
        addEl(){
            let top = `${100 + Math.random() * 500}px`;
            let left = `${100 + Math.random() * 500}px`;
            let id = `item_test${parseInt(Math.random() * 100)}`
            let newEl = {
                id: id,
                sourceAnchors: ['BottomCenter'],
                targetAnchors: ['TopCenter'],
                style:{
                    top: top,
                    left: left
                }
            };
            this.elementsList.push(newEl);
            this.$nextTick(() => {
                this.jsPlumbAddItem(newEl);
            })
        },
        clearElCon(){
            let _this = this;
            // 删除与“item_test1”的所有连接
            _this._jsPlumb.deleteConnectionsForElement('item_test1')
        },
        removeEl(){
            let _this = this;
            _this._jsPlumb.remove('item_test1')
        },
        /*
        * 参考文档:https://jsplumbtoolkit.com/community/doc/connections.html#sourcefilter
        * */
        addConFromData(){
            let _this = this;
            this.elementsList.map( (item,index) => {
                if(index !== 0 && _this.whetherConnect(_this.elementsList[0].id,item.id)){
                    _this._jsPlumb.connect({
                        source:_this.elementsList[0].id,
                        target:item.id,
                        anchors:["BottomCenter", "TopCenter" ],
                    },_this.commonOption);
                }
            })
        },
        reset(){
            let _this = this;
            _this._jsPlumb.deleteEveryConnection();
        }
    },
    mounted(){
        this.jsPlumbInit();
    }
})


</script>
</body>
</html>